/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"SourceBuffer.h"#include<algorithm>#include<cmath>#include<cstring>#include"mozilla/Likely.h"#include"nsIInputStream.h"#include"MainThreadUtils.h"#include"SurfaceCache.h"usingstd::max;usingstd::min;namespacemozilla{namespaceimage{//////////////////////////////////////////////////////////////////////////////// SourceBufferIterator implementation.//////////////////////////////////////////////////////////////////////////////SourceBufferIterator::~SourceBufferIterator(){if(mOwner){mOwner->OnIteratorRelease();}}SourceBufferIterator&SourceBufferIterator::operator=(SourceBufferIterator&&aOther){if(mOwner){mOwner->OnIteratorRelease();}mOwner=Move(aOther.mOwner);mState=aOther.mState;mData=aOther.mData;mChunkCount=aOther.mChunkCount;mByteCount=aOther.mByteCount;return*this;}SourceBufferIterator::StateSourceBufferIterator::AdvanceOrScheduleResume(size_taRequestedBytes,IResumable*aConsumer){MOZ_ASSERT(mOwner);if(MOZ_UNLIKELY(!HasMore())){MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");returnCOMPLETE;}// The range of data [mOffset, mOffset + mNextReadLength) has just been read// by the caller (or at least they don't have any interest in it), so consume// that data.MOZ_ASSERT(mData.mIterating.mNextReadLength<=mData.mIterating.mAvailableLength);mData.mIterating.mOffset+=mData.mIterating.mNextReadLength;mData.mIterating.mAvailableLength-=mData.mIterating.mNextReadLength;mData.mIterating.mNextReadLength=0;if(MOZ_LIKELY(mState==READY)){// If the caller wants zero bytes of data, that's easy enough; we just// configured ourselves for a zero-byte read above! In theory we could do// this even in the START state, but it's not important for performance and// breaking the ability of callers to assert that the pointer returned by// Data() is non-null doesn't seem worth it.if(aRequestedBytes==0){MOZ_ASSERT(mData.mIterating.mNextReadLength==0);returnREADY;}// Try to satisfy the request out of our local buffer. This is potentially// much faster than requesting data from our owning SourceBuffer because we// don't have to take the lock. Note that if we have anything at all in our// local buffer, we use it to satisfy the request; @aRequestedBytes is just// the *maximum* number of bytes we can return.if(mData.mIterating.mAvailableLength>0){returnAdvanceFromLocalBuffer(aRequestedBytes);}}// Our local buffer is empty, so we'll have to request data from our owning// SourceBuffer.returnmOwner->AdvanceIteratorOrScheduleResume(*this,aRequestedBytes,aConsumer);}boolSourceBufferIterator::RemainingBytesIsNoMoreThan(size_taBytes)const{MOZ_ASSERT(mOwner);returnmOwner->RemainingBytesIsNoMoreThan(*this,aBytes);}//////////////////////////////////////////////////////////////////////////////// SourceBuffer implementation.//////////////////////////////////////////////////////////////////////////////constsize_tSourceBuffer::MIN_CHUNK_CAPACITY;SourceBuffer::SourceBuffer():mMutex("image::SourceBuffer"),mConsumerCount(0){}SourceBuffer::~SourceBuffer(){MOZ_ASSERT(mConsumerCount==0,"SourceBuffer destroyed with active consumers");}nsresultSourceBuffer::AppendChunk(Maybe<Chunk>&&aChunk){mMutex.AssertCurrentThreadOwns();#ifdef DEBUGif(mChunks.Length()>0){NS_WARNING("Appending an extra chunk for SourceBuffer");}#endifif(MOZ_UNLIKELY(!aChunk)){returnNS_ERROR_OUT_OF_MEMORY;}if(MOZ_UNLIKELY(aChunk->AllocationFailed())){returnNS_ERROR_OUT_OF_MEMORY;}if(MOZ_UNLIKELY(!mChunks.AppendElement(Move(*aChunk),fallible))){returnNS_ERROR_OUT_OF_MEMORY;}returnNS_OK;}Maybe<SourceBuffer::Chunk>SourceBuffer::CreateChunk(size_taCapacity,boolaRoundUp/* = true */){if(MOZ_UNLIKELY(aCapacity==0)){MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?");returnNothing();}// Round up if requested.size_tfinalCapacity=aRoundUp?RoundedUpCapacity(aCapacity):aCapacity;// Use the size of the SurfaceCache as an additional heuristic to avoid// allocating huge buffers. Generally images do not get smaller when decoded,// so if we could store the source data in the SurfaceCache, we assume that// there's no way we'll be able to store the decoded version.if(MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity))){NS_WARNING("SourceBuffer refused to create chunk too large for SurfaceCache");returnNothing();}returnSome(Chunk(finalCapacity));}nsresultSourceBuffer::Compact(){mMutex.AssertCurrentThreadOwns();MOZ_ASSERT(mConsumerCount==0,"Should have no consumers here");MOZ_ASSERT(mWaitingConsumers.Length()==0,"Shouldn't have waiters");MOZ_ASSERT(mStatus,"Should be complete here");// Compact our waiting consumers list, since we're complete and no future// consumer will ever have to wait.mWaitingConsumers.Compact();// If we have no chunks, then there's nothing to compact.if(mChunks.Length()<1){returnNS_OK;}// If we have one chunk, then we can compact if it has excess capacity.if(mChunks.Length()==1&&mChunks[0].Length()==mChunks[0].Capacity()){returnNS_OK;}// We can compact our buffer. Determine the total length.size_tlength=0;for(uint32_ti=0;i<mChunks.Length();++i){length+=mChunks[i].Length();}// If our total length is zero (which means ExpectLength() got called, but no// data ever actually got written) then just empty our chunk list.if(MOZ_UNLIKELY(length==0)){mChunks.Clear();returnNS_OK;}Maybe<Chunk>newChunk=CreateChunk(length,/* aRoundUp = */false);if(MOZ_UNLIKELY(!newChunk||newChunk->AllocationFailed())){NS_WARNING("Failed to allocate chunk for SourceBuffer compacting - OOM?");returnNS_OK;}// Copy our old chunks into the new chunk.for(uint32_ti=0;i<mChunks.Length();++i){size_toffset=newChunk->Length();MOZ_ASSERT(offset<newChunk->Capacity());MOZ_ASSERT(offset+mChunks[i].Length()<=newChunk->Capacity());memcpy(newChunk->Data()+offset,mChunks[i].Data(),mChunks[i].Length());newChunk->AddLength(mChunks[i].Length());}MOZ_ASSERT(newChunk->Length()==newChunk->Capacity(),"Compacted chunk has slack space");// Replace the old chunks with the new, compact chunk.mChunks.Clear();if(MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(newChunk))))){returnHandleError(NS_ERROR_OUT_OF_MEMORY);}mChunks.Compact();returnNS_OK;}/* static */size_tSourceBuffer::RoundedUpCapacity(size_taCapacity){// Protect against overflow.if(MOZ_UNLIKELY(SIZE_MAX-aCapacity<MIN_CHUNK_CAPACITY)){returnaCapacity;}// Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the// size of a page).size_troundedCapacity=(aCapacity+MIN_CHUNK_CAPACITY-1)&~(MIN_CHUNK_CAPACITY-1);MOZ_ASSERT(roundedCapacity>=aCapacity,"Bad math?");MOZ_ASSERT(roundedCapacity-aCapacity<MIN_CHUNK_CAPACITY,"Bad math?");returnroundedCapacity;}size_tSourceBuffer::FibonacciCapacityWithMinimum(size_taMinCapacity){mMutex.AssertCurrentThreadOwns();// We grow the source buffer using a Fibonacci growth rate.size_tlength=mChunks.Length();if(length==0){returnaMinCapacity;}if(length==1){returnmax(2*mChunks[0].Capacity(),aMinCapacity);}returnmax(mChunks[length-1].Capacity()+mChunks[length-2].Capacity(),aMinCapacity);}voidSourceBuffer::AddWaitingConsumer(IResumable*aConsumer){mMutex.AssertCurrentThreadOwns();MOZ_ASSERT(!mStatus,"Waiting when we're complete?");if(aConsumer){mWaitingConsumers.AppendElement(aConsumer);}}voidSourceBuffer::ResumeWaitingConsumers(){mMutex.AssertCurrentThreadOwns();if(mWaitingConsumers.Length()==0){return;}for(uint32_ti=0;i<mWaitingConsumers.Length();++i){mWaitingConsumers[i]->Resume();}mWaitingConsumers.Clear();}nsresultSourceBuffer::ExpectLength(size_taExpectedLength){MOZ_ASSERT(aExpectedLength>0,"Zero expected size?");MutexAutoLocklock(mMutex);if(MOZ_UNLIKELY(mStatus)){MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete");returnNS_OK;}if(MOZ_UNLIKELY(mChunks.Length()>0)){MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength");returnNS_OK;}if(MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))){returnHandleError(NS_ERROR_OUT_OF_MEMORY);}returnNS_OK;}nsresultSourceBuffer::Append(constchar*aData,size_taLength){MOZ_ASSERT(aData,"Should have a buffer");MOZ_ASSERT(aLength>0,"Writing a zero-sized chunk");size_tcurrentChunkCapacity=0;size_tcurrentChunkLength=0;char*currentChunkData=nullptr;size_tcurrentChunkRemaining=0;size_tforCurrentChunk=0;size_tforNextChunk=0;size_tnextChunkCapacity=0;{MutexAutoLocklock(mMutex);if(MOZ_UNLIKELY(mStatus)){// This SourceBuffer is already complete; ignore further data.returnNS_ERROR_FAILURE;}if(MOZ_UNLIKELY(mChunks.Length()==0)){if(MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))){returnHandleError(NS_ERROR_OUT_OF_MEMORY);}}// Copy out the current chunk's information so we can release the lock.// Note that this wouldn't be safe if multiple producers were allowed!Chunk¤tChunk=mChunks.LastElement();currentChunkCapacity=currentChunk.Capacity();currentChunkLength=currentChunk.Length();currentChunkData=currentChunk.Data();// Partition this data between the current chunk and the next chunk.// (Because we always allocate a chunk big enough to fit everything passed// to Append, we'll never need more than those two chunks to store// everything.)currentChunkRemaining=currentChunkCapacity-currentChunkLength;forCurrentChunk=min(aLength,currentChunkRemaining);forNextChunk=aLength-forCurrentChunk;// If we'll need another chunk, determine what its capacity should be while// we still hold the lock.nextChunkCapacity=forNextChunk>0?FibonacciCapacityWithMinimum(forNextChunk):0;}// Write everything we can fit into the current chunk.MOZ_ASSERT(currentChunkLength+forCurrentChunk<=currentChunkCapacity);memcpy(currentChunkData+currentChunkLength,aData,forCurrentChunk);// If there's something left, create a new chunk and write it there.Maybe<Chunk>nextChunk;if(forNextChunk>0){MOZ_ASSERT(nextChunkCapacity>=forNextChunk,"Next chunk too small?");nextChunk=CreateChunk(nextChunkCapacity);if(MOZ_LIKELY(nextChunk&&!nextChunk->AllocationFailed())){memcpy(nextChunk->Data(),aData+forCurrentChunk,forNextChunk);nextChunk->AddLength(forNextChunk);}}// Update shared data structures.{MutexAutoLocklock(mMutex);// Update the length of the current chunk.Chunk¤tChunk=mChunks.LastElement();MOZ_ASSERT(currentChunk.Data()==currentChunkData,"Multiple producers?");MOZ_ASSERT(currentChunk.Length()==currentChunkLength,"Multiple producers?");currentChunk.AddLength(forCurrentChunk);// If we created a new chunk, add it to the series.if(forNextChunk>0){if(MOZ_UNLIKELY(!nextChunk)){returnHandleError(NS_ERROR_OUT_OF_MEMORY);}if(MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(nextChunk))))){returnHandleError(NS_ERROR_OUT_OF_MEMORY);}}// Resume any waiting readers now that there's new data.ResumeWaitingConsumers();}returnNS_OK;}staticnsresultAppendToSourceBuffer(nsIInputStream*,void*aClosure,constchar*aFromRawSegment,uint32_t,uint32_taCount,uint32_t*aWriteCount){SourceBuffer*sourceBuffer=static_cast<SourceBuffer*>(aClosure);// Copy the source data. Unless we hit OOM, we squelch the return value here,// because returning an error means that ReadSegments stops reading data, and// we want to ensure that we read everything we get. If we hit OOM then we// return a failed status to the caller.nsresultrv=sourceBuffer->Append(aFromRawSegment,aCount);if(rv==NS_ERROR_OUT_OF_MEMORY){returnrv;}// Report that we wrote everything we got.*aWriteCount=aCount;returnNS_OK;}nsresultSourceBuffer::AppendFromInputStream(nsIInputStream*aInputStream,uint32_taCount){uint32_tbytesRead;nsresultrv=aInputStream->ReadSegments(AppendToSourceBuffer,this,aCount,&bytesRead);if(!NS_WARN_IF(NS_FAILED(rv))){MOZ_ASSERT(bytesRead==aCount,"AppendToSourceBuffer should consume everything");}returnrv;}voidSourceBuffer::Complete(nsresultaStatus){MutexAutoLocklock(mMutex);if(MOZ_UNLIKELY(mStatus)){MOZ_ASSERT_UNREACHABLE("Called Complete more than once");return;}if(MOZ_UNLIKELY(NS_SUCCEEDED(aStatus)&&IsEmpty())){// It's illegal to succeed without writing anything.aStatus=NS_ERROR_FAILURE;}mStatus=Some(aStatus);// Resume any waiting consumers now that we're complete.ResumeWaitingConsumers();// If we still have active consumers, just return.if(mConsumerCount>0){return;}// Attempt to compact our buffer down to a single chunk.Compact();}boolSourceBuffer::IsComplete(){MutexAutoLocklock(mMutex);returnbool(mStatus);}size_tSourceBuffer::SizeOfIncludingThisWithComputedFallback(MallocSizeOfaMallocSizeOf)const{MutexAutoLocklock(mMutex);size_tn=aMallocSizeOf(this);n+=mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);for(uint32_ti=0;i<mChunks.Length();++i){size_tchunkSize=aMallocSizeOf(mChunks[i].Data());if(chunkSize==0){// We're on a platform where moz_malloc_size_of always returns 0.chunkSize=mChunks[i].Capacity();}n+=chunkSize;}returnn;}SourceBufferIteratorSourceBuffer::Iterator(){{MutexAutoLocklock(mMutex);mConsumerCount++;}returnSourceBufferIterator(this);}voidSourceBuffer::OnIteratorRelease(){MutexAutoLocklock(mMutex);MOZ_ASSERT(mConsumerCount>0,"Consumer count doesn't add up");mConsumerCount--;// If we still have active consumers, or we're not complete yet, then return.if(mConsumerCount>0||!mStatus){return;}// Attempt to compact our buffer down to a single chunk.Compact();}boolSourceBuffer::RemainingBytesIsNoMoreThan(constSourceBufferIterator&aIterator,size_taBytes)const{MutexAutoLocklock(mMutex);// If we're not complete, we always say no.if(!mStatus){returnfalse;}// If the iterator's at the end, the answer is trivial.if(!aIterator.HasMore()){returntrue;}uint32_titeratorChunk=aIterator.mData.mIterating.mChunk;size_titeratorOffset=aIterator.mData.mIterating.mOffset;size_titeratorLength=aIterator.mData.mIterating.mAvailableLength;// Include the bytes the iterator is currently pointing to in the limit, so// that the current chunk doesn't have to be a special case.size_tbytes=aBytes+iteratorOffset+iteratorLength;// Count the length over all of our chunks, starting with the one that the// iterator is currently pointing to. (This is O(N), but N is expected to be// ~1, so it doesn't seem worth caching the length separately.)size_tlengthSoFar=0;for(uint32_ti=iteratorChunk;i<mChunks.Length();++i){lengthSoFar+=mChunks[i].Length();if(lengthSoFar>bytes){returnfalse;}}returntrue;}SourceBufferIterator::StateSourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator&aIterator,size_taRequestedBytes,IResumable*aConsumer){MutexAutoLocklock(mMutex);MOZ_ASSERT(aIterator.HasMore(),"Advancing a completed iterator and ""AdvanceOrScheduleResume didn't catch it");if(MOZ_UNLIKELY(mStatus&&NS_FAILED(*mStatus))){// This SourceBuffer is complete due to an error; all reads fail.returnaIterator.SetComplete(*mStatus);}if(MOZ_UNLIKELY(mChunks.Length()==0)){// We haven't gotten an initial chunk yet.AddWaitingConsumer(aConsumer);returnaIterator.SetWaiting();}uint32_titeratorChunkIdx=aIterator.mData.mIterating.mChunk;MOZ_ASSERT(iteratorChunkIdx<mChunks.Length());constChunk¤tChunk=mChunks[iteratorChunkIdx];size_titeratorEnd=aIterator.mData.mIterating.mOffset+aIterator.mData.mIterating.mAvailableLength;MOZ_ASSERT(iteratorEnd<=currentChunk.Length());MOZ_ASSERT(iteratorEnd<=currentChunk.Capacity());if(iteratorEnd<currentChunk.Length()){// There's more data in the current chunk.returnaIterator.SetReady(iteratorChunkIdx,currentChunk.Data(),iteratorEnd,currentChunk.Length()-iteratorEnd,aRequestedBytes);}if(iteratorEnd==currentChunk.Capacity()&&!IsLastChunk(iteratorChunkIdx)){// Advance to the next chunk.constChunk&nextChunk=mChunks[iteratorChunkIdx+1];returnaIterator.SetReady(iteratorChunkIdx+1,nextChunk.Data(),0,nextChunk.Length(),aRequestedBytes);}MOZ_ASSERT(IsLastChunk(iteratorChunkIdx),"Should've advanced");if(mStatus){// There's no more data and this SourceBuffer completed successfully.MOZ_ASSERT(NS_SUCCEEDED(*mStatus),"Handled failures earlier");returnaIterator.SetComplete(*mStatus);}// We're not complete, but there's no more data right now. Arrange to wake up// the consumer when we get more data.AddWaitingConsumer(aConsumer);returnaIterator.SetWaiting();}nsresultSourceBuffer::HandleError(nsresultaError){MOZ_ASSERT(NS_FAILED(aError),"Should have an error here");MOZ_ASSERT(aError==NS_ERROR_OUT_OF_MEMORY,"Unexpected error; may want to notify waiting readers, which ""HandleError currently doesn't do");mMutex.AssertCurrentThreadOwns();NS_WARNING("SourceBuffer encountered an unrecoverable error");// Record the error.mStatus=Some(aError);// Drop our references to waiting readers.mWaitingConsumers.Clear();return*mStatus;}boolSourceBuffer::IsEmpty(){mMutex.AssertCurrentThreadOwns();returnmChunks.Length()==0||mChunks[0].Length()==0;}boolSourceBuffer::IsLastChunk(uint32_taChunk){mMutex.AssertCurrentThreadOwns();returnaChunk+1==mChunks.Length();}}// namespace image}// namespace mozilla